
#if defined(WIN32) || defined(WIN64)
#pragma unmanaged
#endif

#include <Liberty3D/Engine/IAssetFile.h>
#include <Liberty3D/Engine/AssetFileFolder.h>
#include <Liberty3D/Engine/Conversion.h>
#include <Liberty3D/Engine/Path.h>

#include <streambuf>
#include <fstream>
#include <sstream>

#ifndef WIN32
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>
#include <Liberty3D/Engine/PosixException.h>
#else
#include <windows.h>
#include <Liberty3D/Engine/Win32Exception.h>
#endif

using namespace Liberty::Engine;

AssetFileFolder::AssetFileFolder(const std::string& folder) : _folder(folder) {
    Path::ensureSuffixSlash(_folder);
}

std::string AssetFileFolder::absolutePath(const std::string& f) const {
    return _folder + f;
}

bool AssetFileFolder::exists(const std::string& resourceName) const {
    std::string fullName(absolutePath(resourceName));
    
#if defined(WIN32) || defined(WIN64)
    Path::winifyPath(fullName);
    
    return GetFileAttributes(UTF8_To_UTF16(fullName).c_str()) != INVALID_FILE_ATTRIBUTES;
#else
    struct stat s;
    
    return stat(fullName.c_str(), &s) == 0;
#endif
}

std::streambuf* AssetFileFolder::open(const std::string& resourceName, std::ios::openmode mode) const {
    std::string fullName(absolutePath(resourceName));

#if defined(WIN32) || defined(WIN64)
    Path::winifyPath(fullName);
#endif

    std::filebuf* buf = new std::filebuf();
    std::filebuf* o;

#if defined(WIN32) || defined(WIN64)
    o = buf->open(UTF8_To_UTF16(fullName).c_str(), mode);
#else
    o = buf->open(fullName.c_str(), mode);
#endif

    if (o == nullptr) {
        delete buf;
        return nullptr;
    }

    return buf;
}

void AssetFileFolder::list(const std::string& folder, std::vector<std::string>& entries) const {
#if defined(WIN32) || defined(WIN64)
    std::string     pattern(absolutePath(folder));
    WIN32_FIND_DATA ffd;

    pattern += "/*.*";
    Path::winifyPath(pattern);

    HANDLE hFind = ::FindFirstFile(UTF8_To_UTF16(pattern).c_str(), &ffd);

    if (hFind == INVALID_HANDLE_VALUE) {
        return;
    }

    do {
        entries.push_back(folder + "/" + UTF16_To_UTF8(ffd.cFileName));
    } while (FindNextFile(hFind, &ffd));
#else
    std::string fullFolder(absolutePath(folder));
    DIR*        folderHandle = opendir(fullFolder.c_str());
    
    if (folderHandle == NULL) {
        return;
    }
    
    struct dirent  entry;
    struct dirent* result;
    
    while (readdir_r(folderHandle, &entry, &result) == 0) {
        if (entry.d_name[0] == '.') {
            continue;
        }
        
        entries.push_back(fullFolder + "/" + entry.d_name);
    }
#endif
}

static bool recursiveMakeDir(const std::string& folder) {
    std::string folderS(folder);

    Path::removeSuffixSlash(folderS);

#ifdef WIN32
    std::wstring folderW(UTF8_To_UTF16(folderS));

    Path::winifyPath(folderW);

    if (CreateDirectory(folderW.c_str(), nullptr)) {
        return true;
    }

    DWORD dwLastError = GetLastError();

    if (dwLastError != ERROR_PATH_NOT_FOUND) {
        throw Win32Exception(dwLastError);
    }
#else
    if (mkdir(folderS.c_str(), 0755) == 0) {
        return true;
    }
    
    if (errno != ENOENT) {
        throw PosixException(errno, "Error making directory \"" + folderS + "\"");
    }
#endif
    
    if (recursiveMakeDir(Path::split(folderS))) {
#ifdef WIN32
        return CreateDirectory(folderW.c_str(), nullptr) == TRUE;
#else
        return mkdir(folderS.c_str(), 0755) == 0;
#endif
    }
    
    return false;
}

void AssetFileFolder::makeDirectory(const std::string& folder, bool createIntermediates) {
    std::string fullName(absolutePath(folder));
    
    Path::removeSuffixSlash(fullName);

#ifdef WIN32
    std::wstring folderW(UTF8_To_UTF16(fullName));

    Path::winifyPath(folderW);

    DWORD s = GetFileAttributes(folderW.c_str());

    if (s != INVALID_FILE_ATTRIBUTES) {
        if (s & FILE_ATTRIBUTE_DIRECTORY) {
            return;
        }

        throw std::runtime_error(folder + " already exist and is not a folder.");
    }
#else
    struct stat s;
    
    if (stat(fullName.c_str(), &s) == 0) {
        if (s.st_mode & S_IFDIR) {
            return;
        }

        throw std::runtime_error(folder + " already exist and is not a folder.");
    }
#endif
    
    if (createIntermediates) {
        if (!recursiveMakeDir(fullName)) {
            throw std::runtime_error("Error making directory \"" + folder + "\"");
        }
    }
    else {
#ifdef WIN32
        if (!CreateDirectory(folderW.c_str(), nullptr)) {
            throw Win32Exception(GetLastError());
        }
#else
        if (mkdir(fullName.c_str(), 0755)) {
            throw PosixException(errno, "Error making directory \"" + folder + "\"");
        }
#endif
    }
}

void AssetFileFolder::remove(const std::string& path, bool recursive) {
    std::string fullName(absolutePath(path));

    Path::removeSuffixSlash(fullName);

#ifdef WIN32
    std::wstring folderW(UTF8_To_UTF16(fullName));

    Path::winifyPath(folderW);

    DWORD s = GetFileAttributes(folderW.c_str());

    if (s != INVALID_FILE_ATTRIBUTES) {
        if (s & FILE_ATTRIBUTE_DIRECTORY) {
            if (!RemoveDirectory(folderW.c_str())) {
                if (!recursive) {
                    throw Win32Exception(GetLastError());
                }
                return;
            }
        }
        else {
            if (!DeleteFile(folderW.c_str())) {
                throw Win32Exception(GetLastError());
            }

            return;
        }
    }
    else {
        throw Win32Exception(GetLastError());
    }
#else
    struct stat s;
    
    if (stat(fullName.c_str(), &s) == 0) {
        if (s.st_mode & S_IFDIR) {
            if (rmdir(fullName.c_str()) != 0) {
                if (!recursive) {
                    throw PosixException(errno, "Error deleting directory \"" + path + "\"");
                }
                return;
            }
        }
        else {
            if (unlink(fullName.c_str()) != 0) {
                throw PosixException(errno, "Error deleting file \"" + path + "\"");
            }

            return;
        }
    }
    else {
        throw PosixException(errno, "Error deleting \"" + path + "\"");
    }
#endif

    // If we end up here, we tried to delete a folder, and we are doing this
    // recursively.

    std::vector<std::string> entries;

    list(path, entries);

    std::vector<std::string>::iterator it, end(entries.end());

    for (it = entries.begin(); it != end; ++it) {
        remove(*it, true);
    }

    // Try again.

#ifdef WIN32
    if (s & FILE_ATTRIBUTE_DIRECTORY) {
        RemoveDirectory(folderW.c_str());
    }
    else {
        DeleteFile(folderW.c_str());
    }
#else
    if (s.st_mode & S_IFDIR) {
        rmdir(fullName.c_str());
    }
    else {
        unlink(fullName.c_str());
    }
#endif
}

void AssetFileFolder::sync() {
}

std::shared_ptr<IAssetFile> IAssetFile::assetFileForFolder(const std::string& folder) {
    return std::shared_ptr<IAssetFile>(new Liberty::Engine::AssetFileFolder(folder));
}
